Imports System
Imports System.Text
Imports System.Collections
Imports System.Collections.Generic
Imports System.Drawing
Imports System.Drawing.Printing
Imports System.Data
Imports System.Globalization
Imports System.Windows.Forms
Imports PdfSharp.Drawing

''' <summary> 
''' Data Grid View Printer. Print functions for a datagridview, since MS didn't see fit to do it. 
''' Source: www.CodeProject.com: "Another DataGridView Printer" by Steven Griffith
''' Note: many modifications and enhancements were made by Chris Wilson for WRDB 5, including ability to print to PDF
''' Enhancements are marked with "LCW" in comment
''' </summary> 
Public Class DGVPrinter

#Region "Global variables"

    ' the data grid view we're printing 
    Private dgv As DataGridView = Nothing

    ' print document 
    Private printDoc As PrintDocument = Nothing

    ' print status items 
    Private RowsToPrint As IList
    Private ColsToPrint As IList
    Private LastRowPrinted As Integer = -1
    Private fromPage As Integer = 0
    Private toPage As Integer = -1

    ' page formatting options 
    Private pageHeight As Integer = 0
    Private pageWidth As Integer = 0
    Private printWidth As Integer = 0
    Private RowHeaderWidth As Single = 0
    Private CurrentPage As Integer = 0
    Private printRange As PrintRange

    ' calculated values 
    Private headerHeight As Single = 0
    Private footerHeight As Single = 0
    Private pageNumberHeight As Single = 0
    Private colheaderheight As Single = 0
    Private coltotalwidth As Single = 0
    Private rowHeights As List(Of Single)
    Private colWidths As List(Of Single)

    ' flag to indicate if should be sent to PDF
    Private sendToPDF As Boolean = False
#End Region

#Region "Properties"

    Public ReadOnly Property DataGridView() As DataGridView
        Get
            Return dgv
        End Get
    End Property

#Region "Title properties"

    ' override flag 
    Private OverrideTitleFormat As Boolean = False

    Private m_Title As String

    ''' <summary> 
    ''' Title for this report. Default is empty. 
    ''' </summary> 
    Public Property Title() As String
        Get
            Return m_Title
        End Get
        Set(ByVal value As String)
            m_Title = value
            printDoc.DocumentName = m_Title
        End Set
    End Property

    Private m_TitleFont As Font

    ''' <summary> 
    ''' Font for the title. Default is Tahoma, 18pt. 
    ''' </summary> 
    Public Property TitleFont() As Font
        Get
            Return m_TitleFont
        End Get
        Set(ByVal value As Font)
            m_TitleFont = value
        End Set
    End Property

    Private m_TitleColor As Color

    ''' <summary> 
    ''' Foreground color for the title. Default is Black 
    ''' </summary> 
    Public Property TitleColor() As Color
        Get
            Return m_TitleColor
        End Get
        Set(ByVal value As Color)
            m_TitleColor = value
        End Set
    End Property

    Private m_TitleAlignment As StringAlignment

    ''' <summary> 
    ''' Allow the user to override the title string alignment. Default value is 
    ''' Alignment - Near; 
    ''' </summary> 
    Public Property TitleAlignment() As StringAlignment
        Get
            Return m_TitleAlignment
        End Get
        Set(ByVal value As StringAlignment)
            m_TitleAlignment = value
            OverrideTitleFormat = True
        End Set
    End Property

    Private m_TitleFormatFlags As StringFormatFlags

    ''' <summary> 
    ''' Allow the user to override the title string format flags. Default values 
    ''' are: FormatFlags - NoWrap, LineLimit, NoClip 
    ''' </summary> 
    Public Property TitleFormatFlags() As StringFormatFlags
        Get
            Return m_TitleFormatFlags
        End Get
        Set(ByVal value As StringFormatFlags)
            m_TitleFormatFlags = value
            OverrideTitleFormat = True
        End Set
    End Property
#End Region

#Region "Subtitle properties"

    ' override flat 
    Private OverrideSubtitleFormat As Boolean = False

    Private m_Subtitle As String

    ''' <summary> 
    ''' SubTitle for this report. Default is empty. 
    ''' </summary> 
    Public Property SubTitle() As String
        Get
            Return m_Subtitle
        End Get
        Set(ByVal value As String)
            m_Subtitle = value
        End Set
    End Property

    Private m_SubtitleFont As Font

    ''' <summary> 
    ''' Font for the subtitle. Default is Tahoma, 12pt. 
    ''' </summary> 
    Public Property SubTitleFont() As Font
        Get
            Return m_SubtitleFont
        End Get
        Set(ByVal value As Font)
            m_SubtitleFont = value
        End Set
    End Property

    Private m_SubtitleColor As Color

    ''' <summary> 
    ''' Foreground color for the subtitle. Default is Black 
    ''' </summary> 
    Public Property SubTitleColor() As Color
        Get
            Return m_SubtitleColor
        End Get
        Set(ByVal value As Color)
            m_SubtitleColor = value
        End Set
    End Property

    Private m_SubTitleAlignment As StringAlignment

    ''' <summary> 
    ''' Allow the user to override the subtitle string alignment. Default value is 
    ''' Alignment - Near; 
    ''' </summary> 
    Public Property SubTitleAlignment() As StringAlignment
        Get
            Return m_SubTitleAlignment
        End Get
        Set(ByVal value As StringAlignment)
            m_SubTitleAlignment = value
            OverrideSubtitleFormat = True
        End Set
    End Property

    Private m_SubTitleFormatFlags As StringFormatFlags

    ''' <summary> 
    ''' Allow the user to override the subtitle string format flags. Default values 
    ''' are: FormatFlags - NoWrap, LineLimit, NoClip 
    ''' </summary> 
    Public Property SubTitleFormatFlags() As StringFormatFlags
        Get
            Return m_SubTitleFormatFlags
        End Get
        Set(ByVal value As StringFormatFlags)
            m_SubTitleFormatFlags = value
            OverrideSubtitleFormat = True
        End Set
    End Property
#End Region

#Region "Subtitle2 properties (by LCW)"

    ' override flat 
    Private OverrideSubtitle2Format As Boolean = False

    Private m_Subtitle2 As String

    ''' <summary> 
    ''' SubTitle for this report. Default is empty. 
    ''' </summary> 
    Public Property SubTitle2() As String
        Get
            Return m_Subtitle2
        End Get
        Set(ByVal value As String)
            m_Subtitle2 = value
        End Set
    End Property

    Private m_Subtitle2Font As Font

    ''' <summary> 
    ''' Font for the subtitle. Default is Tahoma, 12pt. 
    ''' </summary> 
    Public Property SubTitle2Font() As Font
        Get
            Return m_Subtitle2Font
        End Get
        Set(ByVal value As Font)
            m_Subtitle2Font = value
        End Set
    End Property

    Private m_Subtitle2Color As Color

    ''' <summary> 
    ''' Foreground color for the subtitle. Default is Black 
    ''' </summary> 
    Public Property SubTitle2Color() As Color
        Get
            Return m_Subtitle2Color
        End Get
        Set(ByVal value As Color)
            m_Subtitle2Color = value
        End Set
    End Property

    Private m_SubTitle2Alignment As StringAlignment

    ''' <summary> 
    ''' Allow the user to override the subtitle string alignment. Default value is 
    ''' Alignment - Near; 
    ''' </summary> 
    Public Property SubTitle2Alignment() As StringAlignment
        Get
            Return m_SubTitle2Alignment
        End Get
        Set(ByVal value As StringAlignment)
            m_SubTitle2Alignment = value
            OverrideSubtitle2Format = True
        End Set
    End Property

    Private m_SubTitle2FormatFlags As StringFormatFlags

    ''' <summary> 
    ''' Allow the user to override the subtitle string format flags. Default values 
    ''' are: FormatFlags - NoWrap, LineLimit, NoClip 
    ''' </summary> 
    Public Property SubTitle2FormatFlags() As StringFormatFlags
        Get
            Return m_SubTitle2FormatFlags
        End Get
        Set(ByVal value As StringFormatFlags)
            m_SubTitle2FormatFlags = value
            OverrideSubtitle2Format = True
        End Set
    End Property
#End Region

#Region "Footer properties"

    ' override flag 
    Private OverrideFooterFormat As Boolean = False

    Private m_Footer As String

    ''' <summary> 
    ''' footer for this report. Default is empty. 
    ''' </summary> 
    Public Property Footer() As String
        Get
            Return m_Footer
        End Get
        Set(ByVal value As String)
            m_Footer = value
        End Set
    End Property

    Private m_FooterFont As Font

    ''' <summary> 
    ''' Font for the footer. Default is Tahoma, 10pt. 
    ''' </summary> 
    Public Property FooterFont() As Font
        Get
            Return m_FooterFont
        End Get
        Set(ByVal value As Font)
            m_FooterFont = value
        End Set
    End Property

    Private m_FooterColor As Color

    ''' <summary> 
    ''' Foreground color for the footer. Default is Black 
    ''' </summary> 
    Public Property FooterColor() As Color
        Get
            Return m_FooterColor
        End Get
        Set(ByVal value As Color)
            m_FooterColor = value
        End Set
    End Property

    Private m_FooterAlignment As StringAlignment

    ''' <summary> 
    ''' Allow the user to override the footer string alignment. Default value is 
    ''' Alignment - Center; 
    ''' </summary> 
    Public Property FooterAlignment() As StringAlignment
        Get
            Return m_FooterAlignment
        End Get
        Set(ByVal value As StringAlignment)
            m_FooterAlignment = value
            OverrideFooterFormat = True
        End Set
    End Property

    Private m_FooterFormatFlags As StringFormatFlags

    ''' <summary> 
    ''' Allow the user to override the footer string format flags. Default values 
    ''' are: FormatFlags - NoWrap, LineLimit, NoClip 
    ''' </summary> 
    Public Property FooterFormatFlags() As StringFormatFlags
        Get
            Return m_FooterFormatFlags
        End Get
        Set(ByVal value As StringFormatFlags)
            m_FooterFormatFlags = value
            OverrideFooterFormat = True
        End Set
    End Property

    Private m_FooterSpacing As Single

    ''' <summary>
    ''' Extra spacing between footer lines (in 100ths of an inch)
    ''' </summary>
    Public Property FooterSpacing() As Single
        Get
            Return m_FooterSpacing
        End Get
        Set(ByVal value As Single)
            m_FooterSpacing = value
        End Set
    End Property
#End Region

#Region "EndNote properties (by LCW)"

    ' override flag 
    Private OverrideEndNoteFormat As Boolean = False

    Private m_EndNote As String

    ''' <summary> 
    ''' EndNote for this report. Default is empty. 
    ''' </summary> 
    Public Property EndNote() As String
        Get
            Return m_EndNote
        End Get
        Set(ByVal value As String)
            m_EndNote = value
        End Set
    End Property

    Private m_EndNoteFont As Font

    ''' <summary> 
    ''' Font for the EndNote. Default is Tahoma, 10pt. 
    ''' </summary> 
    Public Property EndNoteFont() As Font
        Get
            Return m_EndNoteFont
        End Get
        Set(ByVal value As Font)
            m_EndNoteFont = value
        End Set
    End Property

    Private m_EndNoteColor As Color

    ''' <summary> 
    ''' Foreground color for the EndNote. Default is Black 
    ''' </summary> 
    Public Property EndNoteColor() As Color
        Get
            Return m_EndNoteColor
        End Get
        Set(ByVal value As Color)
            m_EndNoteColor = value
        End Set
    End Property

    Private m_EndNoteAlignment As StringAlignment

    ''' <summary> 
    ''' Allow the user to override the EndNote string alignment. Default value is 
    ''' Alignment - Center; 
    ''' </summary> 
    Public Property EndNoteAlignment() As StringAlignment
        Get
            Return m_EndNoteAlignment
        End Get
        Set(ByVal value As StringAlignment)
            m_EndNoteAlignment = value
            OverrideEndNoteFormat = True
        End Set
    End Property

    Private m_EndNoteFormatFlags As StringFormatFlags

    ''' <summary> 
    ''' Allow the user to override the EndNote string format flags. Default values 
    ''' are: FormatFlags - NoWrap, LineLimit, NoClip 
    ''' </summary> 
    Public Property EndNoteFormatFlags() As StringFormatFlags
        Get
            Return m_EndNoteFormatFlags
        End Get
        Set(ByVal value As StringFormatFlags)
            m_EndNoteFormatFlags = value
            OverrideEndNoteFormat = True
        End Set
    End Property

    Private m_EndNoteSpacing As Single

    ''' <summary>
    ''' Extra spacing between EndNote lines (in 100ths of an inch)
    ''' </summary>
    Public Property EndNoteSpacing() As Single
        Get
            Return m_EndNoteSpacing
        End Get
        Set(ByVal value As Single)
            m_EndNoteSpacing = value
        End Set
    End Property
#End Region

#Region "Page number properties"

    ' override flag 
    Private OverridePageNumberFormat As Boolean = False

    Private m_NumberPages As Boolean = True

    ''' <summary> 
    ''' Include page number in the printout. Default is true. 
    ''' </summary> 
    Public Property NumberPages() As Boolean
        Get
            Return m_NumberPages
        End Get
        Set(ByVal value As Boolean)
            m_NumberPages = value
        End Set
    End Property

    Private m_PageNumberFont As Font

    ''' <summary> 
    ''' Font for the page number, Default is Tahoma, 8pt. 
    ''' </summary> 
    Public Property PageNumberFont() As Font
        Get
            Return m_PageNumberFont
        End Get
        Set(ByVal value As Font)
            m_PageNumberFont = value
        End Set
    End Property

    Private m_PageNumberColor As Color

    ''' <summary> 
    ''' Text color (foreground) for the page number. Default is Black 
    ''' </summary> 
    Public Property PageNumberColor() As Color
        Get
            Return m_PageNumberColor
        End Get
        Set(ByVal value As Color)
            m_PageNumberColor = value
        End Set
    End Property

    Private m_PageNumberAlignment As StringAlignment

    ''' <summary> 
    ''' Allow the user to override the page number string alignment. Default value is 
    ''' Alignment - Near; 
    ''' </summary> 
    Public Property PageNumberAlignment() As StringAlignment
        Get
            Return m_PageNumberAlignment
        End Get
        Set(ByVal value As StringAlignment)
            m_PageNumberAlignment = value
            OverridePageNumberFormat = True
        End Set
    End Property

    Private m_PageNumberFormatFlags As StringFormatFlags

    ''' <summary> 
    ''' Allow the user to override the pagenumber string format flags. Default values 
    ''' are: FormatFlags - NoWrap, LineLimit, NoClip 
    ''' </summary> 
    Public Property PageNumberFormatFlags() As StringFormatFlags
        Get
            Return m_PageNumberFormatFlags
        End Get
        Set(ByVal value As StringFormatFlags)
            m_PageNumberFormatFlags = value
            OverridePageNumberFormat = True
        End Set
    End Property

    Private m_PageNumberInHeader As Boolean = False

    ''' <summary> 
    ''' Allow the user to select whether to have the page number at the top or bottom 
    ''' of the page. Default is false: page numbers on the bottom of the page 
    ''' </summary> 
    Public Property PageNumberInHeader() As Boolean
        Get
            Return m_PageNumberInHeader
        End Get
        Set(ByVal value As Boolean)
            m_PageNumberInHeader = value
        End Set
    End Property

    Private m_PageNumberOnSeparateLine As Boolean = False

    ''' <summary> 
    ''' Should the page number be printed on a separate line, or printed on the 
    ''' same line as the header / footer? Default is false; 
    ''' </summary> 
    Public Property PageNumberOnSeparateLine() As Boolean
        Get
            Return m_PageNumberOnSeparateLine
        End Get
        Set(ByVal value As Boolean)
            m_PageNumberOnSeparateLine = value
        End Set
    End Property


#End Region

#Region "Header cell properties"

    ' override flag 
    Private OverrideCellHeaderFormat As Boolean

    Private m_HeaderCellAlignment As StringAlignment

    ''' <summary> 
    ''' Allow the user to override the header cell string alignment. Default value is 
    ''' Alignment - Near; 
    ''' </summary> 
    Public Property HeaderCellAlignment() As StringAlignment
        Get
            Return m_HeaderCellAlignment
        End Get
        Set(ByVal value As StringAlignment)
            m_HeaderCellAlignment = value
            OverrideCellHeaderFormat = True
        End Set
    End Property

    Private m_HeaderCellFormatFlags As StringFormatFlags

    ''' <summary> 
    ''' Allow the user to override the header cell string format flags. Default values 
    ''' are: FormatFlags - NoWrap, LineLimit, NoClip 
    ''' </summary> 
    Public Property HeaderCellFormatFlags() As StringFormatFlags
        Get
            Return m_HeaderCellFormatFlags
        End Get
        Set(ByVal value As StringFormatFlags)
            m_HeaderCellFormatFlags = value
            OverrideCellHeaderFormat = True
        End Set
    End Property
#End Region

#Region "Cell properties"

    Private OverrideCellFormat As Boolean = False 'True

    Private m_CellAlignment As StringAlignment

    ''' <summary> 
    ''' Allow the user to override the cell string alignment. Default value is 
    ''' Alignment - Near; 
    ''' </summary> 
    Public Property CellAlignment() As StringAlignment
        Get
            Return m_CellAlignment
        End Get
        Set(ByVal value As StringAlignment)
            m_CellAlignment = value
            OverrideCellFormat = True
        End Set
    End Property

    Private m_CellFormatFlags As StringFormatFlags

    ''' <summary> 
    ''' Allow the user to override the cell string format flags. Default values 
    ''' are: FormatFlags - NoWrap, LineLimit, NoClip 
    ''' </summary> 
    Public Property CellFormatFlags() As StringFormatFlags
        Get
            Return m_CellFormatFlags
        End Get
        Set(ByVal value As StringFormatFlags)
            m_CellFormatFlags = value
            OverrideCellFormat = True
        End Set
    End Property

    ''' <summary>
    ''' Allow caller to specify special overflow flag that allows overflow of text into adjoining cells. 
    ''' </summary>
    Public Property IsOverflowCell(ByVal columnIndex As Integer, ByVal rowIndex As Integer) As Boolean
        Get
            Return dgv.Item(columnIndex, rowIndex).Tag = "Overflow"
        End Get
        Set(ByVal value As Boolean)
            dgv.Item(columnIndex, rowIndex).Tag = IIf(value, "Overflow", Nothing)
        End Set
    End Property
#End Region

#Region "Page level properties"

    Private m_PrintMargins As Margins

    'now is taken from PrintDoc

    '''' <summary> 
    '''' Page margins override in 1/100ths inch. Default is (60, 60, 40, 40) 
    '''' </summary> 
    'Public Property PrintMargins() As Margins
    '    Get
    '        Return m_PrintMargins
    '    End Get
    '    Set(ByVal value As Margins)
    '        m_PrintMargins = value
    '    End Set
    'End Property

    Private m_ProportionalColumns As Boolean = False

    ''' <summary> 
    ''' Spread the columns proportionally across the page so it fits the width. Default is false. 
    ''' </summary> 
    Public Property ProportionalColumns() As Boolean
        Get
            Return m_ProportionalColumns
        End Get
        Set(ByVal value As Boolean)
            m_ProportionalColumns = value
        End Set
    End Property

    Private m_AllowWordWrap As Boolean = False

    ''' <summary> 
    ''' If ProportionalColumns is true, will recompute row heights and wrap text. Default is false. 
    ''' </summary> 
    Public Property AllowWordWrap() As Boolean
        Get
            Return m_AllowWordWrap
        End Get
        Set(ByVal value As Boolean)
            m_AllowWordWrap = value
        End Set
    End Property

    Private m_FontSizeMult As Single = 1.0
    Private m_AutoReduceFont As Boolean = False

    ''' <summary> 
    ''' If ProportionalColumns is true, will auto-reduce font sizes to 80% or original to help improve fit
    ''' </summary> 
    Public Property AutoReduceFont() As Boolean
        Get
            Return m_AutoReduceFont
        End Get
        Set(ByVal value As Boolean)
            m_AutoReduceFont = value
        End Set
    End Property

    Private m_ForceLandscape As Boolean = False

    ''' <summary> 
    ''' Force the report to be printed in landscape mode
    ''' </summary> 
    Public Property ForceLandscape() As Boolean
        Get
            Return m_ForceLandscape
        End Get
        Set(ByVal value As Boolean)
            m_ForceLandscape = value
        End Set
    End Property

    Private m_RepeatTitles As Boolean

    ''' <summary>
    ''' Repeat title and subtitles on all pages. Default is true.
    ''' </summary>
    Public Property RepeatTitles() As Boolean
        Get
            Return m_RepeatTitles
        End Get
        Set(ByVal value As Boolean)
            m_RepeatTitles = value
        End Set
    End Property
#End Region

#End Region

    ''' <summary> 
    ''' Constructor for DGVPrinter 
    ''' </summary> 
    Public Sub New(ByRef _dgv As DataGridView, ByRef _PrintDoc As PrintDocument)
        Initialize(_dgv, _PrintDoc)
    End Sub

    Public Sub New()

    End Sub

    Public Sub Initialize(ByRef _dgv As DataGridView, ByRef _PrintDoc As PrintDocument)
        dgv = _dgv

        ' create print document 
        If _PrintDoc Is Nothing Then printDoc = New PrintDocument() Else printDoc = _PrintDoc

        'AddHandler printDoc.PrintPage, AddressOf printDoc_PrintPage
        m_PrintMargins = New Margins(60, 60, 40, 40)

        ' set default fonts 
        m_PageNumberFont = New Font("Tahoma", 8, FontStyle.Regular, GraphicsUnit.Point)
        m_PageNumberColor = Color.Black
        m_TitleFont = New Font("Tahoma", 16, FontStyle.Bold, GraphicsUnit.Point)
        m_TitleColor = Color.Black
        m_SubtitleFont = New Font("Tahoma", 14, FontStyle.Bold, GraphicsUnit.Point)
        m_SubtitleColor = Color.Black
        m_Subtitle2Font = New Font("Tahoma", 12, FontStyle.Regular, GraphicsUnit.Point)
        m_Subtitle2Color = Color.Black
        m_FooterFont = New Font("Tahoma", 10, FontStyle.Bold, GraphicsUnit.Point)
        m_FooterColor = Color.Black
        m_EndNoteFont = New Font("Tahoma", 8, FontStyle.Regular, GraphicsUnit.Point)
        m_EndNoteColor = Color.Black

        ' set default string formats 
        m_TitleAlignment = StringAlignment.Center
        m_TitleFormatFlags = StringFormatFlags.NoWrap Or StringFormatFlags.LineLimit Or StringFormatFlags.NoClip

        m_SubTitleAlignment = StringAlignment.Center
        m_SubTitleFormatFlags = StringFormatFlags.NoWrap Or StringFormatFlags.LineLimit Or StringFormatFlags.NoClip

        m_SubTitle2Alignment = StringAlignment.Center
        m_SubTitle2FormatFlags = StringFormatFlags.NoWrap Or StringFormatFlags.LineLimit Or StringFormatFlags.NoClip

        m_FooterAlignment = StringAlignment.Center
        m_FooterFormatFlags = StringFormatFlags.NoWrap Or StringFormatFlags.LineLimit Or StringFormatFlags.NoClip

        m_EndNoteAlignment = StringAlignment.Near
        m_EndNoteFormatFlags = StringFormatFlags.NoWrap Or StringFormatFlags.LineLimit Or StringFormatFlags.NoClip

        m_PageNumberAlignment = StringAlignment.Far
        m_PageNumberFormatFlags = StringFormatFlags.NoWrap Or StringFormatFlags.LineLimit Or StringFormatFlags.NoClip

        m_HeaderCellAlignment = StringAlignment.Near
        m_HeaderCellFormatFlags = StringFormatFlags.NoWrap Or StringFormatFlags.LineLimit Or StringFormatFlags.NoClip

        m_CellAlignment = StringAlignment.Near
        m_CellFormatFlags = StringFormatFlags.NoWrap Or StringFormatFlags.LineLimit Or StringFormatFlags.NoClip

        m_RepeatTitles = True
    End Sub

    ''' <summary> 
    ''' Start the printing process, print to a printer. 
    ''' </summary> 
    Public Sub PrintDataGridView(Optional ByVal Preview As Boolean = False)

        Dim res As DialogResult

        AddHandler printDoc.PrintPage, AddressOf printDoc_PrintPage

        ' show print dialog 
        If Preview Then
            Dim pp As New MyPrintPreviewDialog(printDoc)
            pp.Document = printDoc
            Do
                SetupPrint()
                res = pp.ShowDialog
            Loop Until res <> DialogResult.Retry
            pp.Dispose()
        Else
            ' create new print dialog and set options 
            Dim pd As New PrintDialog()
            pd.Document = printDoc
            printDoc.DefaultPageSettings.Margins = m_PrintMargins
            pd.AllowSelection = True
            pd.AllowSomePages = True
            pd.AllowCurrentPage = True
            pd.AllowPrintToFile = False
            res = pd.ShowDialog
        End If
        If res = DialogResult.OK Then
            SetupPrint()
            printDoc.Print()
        End If

        RemoveHandler printDoc.PrintPage, AddressOf printDoc_PrintPage
    End Sub

    ''' <summary> 
    ''' Start the printing process, print to a PDF document. 
    ''' </summary> 
    Public Sub PrintDataGridViewToPDF(Optional ByVal Preview As Boolean = False, Optional ByVal DefaultFileName As String = "")

        ' create new print dialog and set options 
        Dim dlg As New SaveFileDialog
        With dlg
            .AddExtension = True
            .DefaultExt = "PDF"
            .Filter = "PDF File (*.pdf)|*.pdf"
            .OverwritePrompt = False
            .SupportMultiDottedExtensions = False
            .Title = "Save to PDF"
            .FileName = DefaultFileName
            If .ShowDialog = DialogResult.OK Then
                'convert margins to points
                With m_PrintMargins
                    .Left *= 0.72
                    .Right *= 0.72
                    .Top *= 0.72
                    .Bottom *= 0.72
                End With

                Dim Append As Boolean = False

                If My.Computer.FileSystem.FileExists(.FileName) Then
                    Select Case MessageBox.Show(String.Format("The file '{0}' already exists. Do you want to overwrite it?\n\nSelect 'Yes' to overwrite, 'No' to append to the existing file.".Replace("\n", vbCr), .FileName), "Warning", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question)
                        Case DialogResult.Yes : My.Computer.FileSystem.DeleteFile(.FileName) : Append = False
                        Case DialogResult.No : Append = True
                        Case DialogResult.Cancel : Exit Sub
                    End Select
                End If
                Dim pdf As PdfSharp.Pdf.PdfDocument
                Dim tempfile As String = My.Computer.FileSystem.GetTempFileName
                If Append Then
                    pdf = PdfSharp.Pdf.IO.PdfReader.Open(.FileName, PdfSharp.Pdf.IO.PdfDocumentOpenMode.Modify)
                Else
                    pdf = New PdfSharp.Pdf.PdfDocument(.FileName)
                End If
                Dim pdfpage As PdfSharp.Pdf.PdfPage
                Dim HasMorePages As Boolean = True
                sendToPDF = True
                Dim gfx As PdfSharp.Drawing.XGraphics
                Do While HasMorePages
                    pdfpage = pdf.AddPage
                    SetupPDF(pdf, pdfpage)
                    gfx = PdfSharp.Drawing.XGraphics.FromPdfPage(pdfpage)
                    PrintPage(gfx, HasMorePages)
                Loop
                Try
                    If Append Then
                        pdf.Save(.FileName.Replace(".pdf", "new.pdf"))
                        pdf.Close()
                    Else
                        pdf.Close()
                        'pdf.Save(.FileName)
                    End If
                Catch ex As Exception
                    MessageBox.Show("Unable to save PDF file; it is likely already open by another user.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Error)
                    .Dispose()
                    Exit Sub
                Finally
                    pdf.Dispose()
                    pdf = Nothing
                End Try
                If Preview Then Process.Start(.FileName)
            End If
            .Dispose()
        End With
    End Sub

    ''' <summary> 
    ''' Set up the print job. Save information from print dialog 
    ''' and print document for easy access. Also sets up the rows 
    ''' and columns that will be printed. 
    ''' </summary> 
    Private Sub SetupPrint()
        '----------------------------------------------------------------- 
        ' save data from print dialog and document 
        '----------------------------------------------------------------- 

        If m_ForceLandscape Then printDoc.DefaultPageSettings.Landscape = True

        ' check to see if we're doing landscape printing 
        If printDoc.DefaultPageSettings.Landscape Then
            ' landscape: switch width and height 
            pageHeight = printDoc.DefaultPageSettings.PaperSize.Width
            pageWidth = printDoc.DefaultPageSettings.PaperSize.Height
        Else
            ' portrait: keep width and height as expected 
            pageHeight = printDoc.DefaultPageSettings.PaperSize.Height
            pageWidth = printDoc.DefaultPageSettings.PaperSize.Width
        End If

        ' save printer margins and calc print width 
        m_PrintMargins = printDoc.DefaultPageSettings.Margins
        printWidth = pageWidth - m_PrintMargins.Left - m_PrintMargins.Right

        ' save print range 
        'printRange = pd.PrinterSettings.PrintRange
        printRange = printDoc.PrinterSettings.PrintRange

        ' pages to print handles "some pages" option 
        If printRange.SomePages = printRange Then
            ' set limits to only print some pages 
            'fromPage = pd.PrinterSettings.FromPage
            'toPage = pd.PrinterSettings.ToPage
            fromPage = printDoc.PrinterSettings.FromPage
            toPage = printDoc.PrinterSettings.ToPage
        Else
            ' set extremes so that we'll print all pages 
            fromPage = 0
            toPage = 2147483647
        End If

        '----------------------------------------------------------------- 
        ' set up the rows and columns to print 
        '----------------------------------------------------------------- 

        ' rows to print (handles "selection" and "current page" options 
        If printRange.Selection = printRange Then
            ' if DGV has rows selected, it's easy, selected rows and all visible columns 
            If 0 <> dgv.SelectedRows.Count Then
                RowsToPrint = dgv.SelectedRows
                ColsToPrint = New List(Of Object)(dgv.Columns.Count)
                For Each col As DataGridViewColumn In dgv.Columns
                    If col.Visible Then
                        ColsToPrint.Add(col)
                    End If
                Next
            ElseIf 0 <> dgv.SelectedColumns.Count Then
                ' if selected columns, then all rows, and selected columns 
                RowsToPrint = dgv.Rows
                ColsToPrint = dgv.SelectedColumns
            Else
                ' we just have a bunch of selected cells so we have to do some work 
                ' set up sorted lists. the selectedcells method does not guarantee 
                ' that the cells will always be in left-right top-bottom order. 
                Dim temprowstoprint As New SortedList(dgv.SelectedCells.Count)
                Dim tempcolstoprint As New SortedList(dgv.SelectedCells.Count)

                ' for each selected cell, add unique rows and columns 
                Dim colindex As Integer, rowindex As Integer
                For Each cell As DataGridViewCell In dgv.SelectedCells
                    colindex = cell.ColumnIndex
                    rowindex = cell.RowIndex

                    If Not temprowstoprint.Contains(rowindex) Then
                        temprowstoprint.Add(rowindex, dgv.Rows(rowindex))
                    End If

                    If Not tempcolstoprint.Contains(colindex) Then
                        tempcolstoprint.Add(colindex, dgv.Columns(colindex))
                    End If
                Next

                ' Move the now-sorted columns and rows to our list of what to print 
                RowsToPrint = New List(Of Object)(temprowstoprint.Count)
                For Each item As Object In temprowstoprint.Values
                    RowsToPrint.Add(item)
                Next
                ColsToPrint = New List(Of Object)(tempcolstoprint.Count)
                For Each item As Object In tempcolstoprint.Values
                    ColsToPrint.Add(item)
                Next
            End If
        ElseIf printRange.CurrentPage = printRange Then
            ' if current page was selected, print visible columns for the 
            ' displayed rows 
            ' create lists 
            RowsToPrint = New List(Of Object)(dgv.DisplayedRowCount(True))
            ColsToPrint = New List(Of Object)(dgv.Columns.Count)
            For i As Integer = dgv.FirstDisplayedScrollingRowIndex To dgv.FirstDisplayedScrollingRowIndex + dgv.DisplayedRowCount(True) - 1

                ' select all visible rows on displayed page 
                Dim row As DataGridViewRow = dgv.Rows(i)
                If row.Visible Then
                    RowsToPrint.Add(row)
                End If
            Next

            ' select all visible columns 
            ColsToPrint = New List(Of Object)(dgv.Columns.Count)
            For Each col As DataGridViewColumn In dgv.Columns
                If col.Visible Then
                    ColsToPrint.Add(col)
                End If
            Next
        Else
            ' this is the default for print all - everything marked visible will be printed 
            ' select all visible rows and all visible columns 
            RowsToPrint = New List(Of Object)(dgv.Rows.Count)
            For Each row As DataGridViewRow In dgv.Rows
                If row.Visible Then
                    RowsToPrint.Add(row)
                End If
            Next

            ColsToPrint = New List(Of Object)(dgv.Columns.Count)
            For Each col As DataGridViewColumn In dgv.Columns
                If col.Visible Then
                    ColsToPrint.Add(col)
                End If
            Next
        End If
    End Sub

    ''' <summary> 
    ''' Set up the print job. Save information from print dialog 
    ''' and print document for easy access. Also sets up the rows 
    ''' and columns that will be printed. 
    ''' </summary> 
    Private Sub SetupPDF(ByVal pdf As PdfSharp.Pdf.PdfDocument, ByVal pdfPage As PdfSharp.Pdf.PdfPage)

        If m_ForceLandscape Then
            pdfPage.Orientation = PdfSharp.PageOrientation.Landscape
            pdfPage.Rotate = 90
        End If
        pdfPage.Size = PdfSharp.PageSize.Letter

        If pdfPage.Orientation = PdfSharp.PageOrientation.Landscape Then
            pageWidth = pdfPage.Height.Point
            pageHeight = pdfPage.Width.Point
            printWidth = pageWidth - m_PrintMargins.Left - m_PrintMargins.Right
        Else
            pageWidth = pdfPage.Width.Point
            pageHeight = pdfPage.Height.Point
            printWidth = pageWidth - m_PrintMargins.Left - m_PrintMargins.Right
        End If

        ' save print range 
        printRange = Printing.PrintRange.AllPages

        ' set extremes so that we'll print all pages 
        fromPage = 0
        toPage = 2147483647

        '----------------------------------------------------------------- 
        ' set up the rows and columns to print 
        '----------------------------------------------------------------- 

        ' rows to print (handles "selection" and "current page" options 
        If printRange.Selection = printRange Then
            ' if DGV has rows selected, it's easy, selected rows and all visible columns 
            If 0 <> dgv.SelectedRows.Count Then
                RowsToPrint = dgv.SelectedRows
                ColsToPrint = New List(Of Object)(dgv.Columns.Count)
                For Each col As DataGridViewColumn In dgv.Columns
                    If col.Visible Then
                        ColsToPrint.Add(col)
                    End If
                Next
            ElseIf 0 <> dgv.SelectedColumns.Count Then
                ' if selected columns, then all rows, and selected columns 
                RowsToPrint = dgv.Rows
                ColsToPrint = dgv.SelectedColumns
            Else
                ' we just have a bunch of selected cells so we have to do some work 
                ' set up sorted lists. the selectedcells method does not guarantee 
                ' that the cells will always be in left-right top-bottom order. 
                Dim temprowstoprint As New SortedList(dgv.SelectedCells.Count)
                Dim tempcolstoprint As New SortedList(dgv.SelectedCells.Count)

                ' for each selected cell, add unique rows and columns 
                Dim colindex As Integer, rowindex As Integer
                For Each cell As DataGridViewCell In dgv.SelectedCells
                    colindex = cell.ColumnIndex
                    rowindex = cell.RowIndex

                    If Not temprowstoprint.Contains(rowindex) Then
                        temprowstoprint.Add(rowindex, dgv.Rows(rowindex))
                    End If

                    If Not tempcolstoprint.Contains(colindex) Then
                        tempcolstoprint.Add(colindex, dgv.Columns(colindex))
                    End If
                Next

                ' Move the now-sorted columns and rows to our list of what to print 
                RowsToPrint = New List(Of Object)(temprowstoprint.Count)
                For Each item As Object In temprowstoprint.Values
                    RowsToPrint.Add(item)
                Next
                ColsToPrint = New List(Of Object)(tempcolstoprint.Count)
                For Each item As Object In tempcolstoprint.Values
                    ColsToPrint.Add(item)
                Next
            End If
        ElseIf printRange.CurrentPage = printRange Then
            ' if current page was selected, print visible columns for the 
            ' displayed rows 
            ' create lists 
            RowsToPrint = New List(Of Object)(dgv.DisplayedRowCount(True))
            ColsToPrint = New List(Of Object)(dgv.Columns.Count)
            For i As Integer = dgv.FirstDisplayedScrollingRowIndex To dgv.FirstDisplayedScrollingRowIndex + dgv.DisplayedRowCount(True) - 1

                ' select all visible rows on displayed page 
                Dim row As DataGridViewRow = dgv.Rows(i)
                If row.Visible Then
                    RowsToPrint.Add(row)
                End If
            Next

            ' select all visible columns 
            ColsToPrint = New List(Of Object)(dgv.Columns.Count)
            For Each col As DataGridViewColumn In dgv.Columns
                If col.Visible Then
                    ColsToPrint.Add(col)
                End If
            Next
        Else
            ' this is the default for print all - everything marked visible will be printed 
            ' select all visible rows and all visible columns 
            RowsToPrint = New List(Of Object)(dgv.Rows.Count)
            For Each row As DataGridViewRow In dgv.Rows
                If row.Visible Then
                    RowsToPrint.Add(row)
                End If
            Next

            ColsToPrint = New List(Of Object)(dgv.Columns.Count)
            For Each col As DataGridViewColumn In dgv.Columns
                If col.Visible Then
                    ColsToPrint.Add(col)
                End If
            Next
        End If
    End Sub

    ''' <summary> 
    ''' Centralize the string format settings. Does the work of checking for user 
    ''' overrides, and if they're not present, setting the cell alignment to match 
    ''' (somewhat) the source control's string alignment. 
    ''' </summary> 
    ''' <param name="alignment">String alignment</param> 
    ''' <param name="flags">String format flags</param> 
    ''' <param name="controlstyle">DataGridView style to apply (if available)</param> 
    ''' <param name="overrideformat">True if user overrode alignment or flags</param> 
    ''' <returns></returns> 
    Private Function ManageStringFormat(ByVal alignment As StringAlignment, ByVal flags As StringFormatFlags, ByVal controlstyle As DataGridViewCellStyle, ByVal overrideformat As Boolean) As Object 'StringFormat
        ' start with the provided 
        'Dim format As New StringFormat()
        Dim format As Object
        If sendToPDF Then
            format = New XStringFormat()
        Else
            format = New StringFormat()
            format.Trimming = StringTrimming.Word
        End If
        format.Alignment = alignment
        format.FormatFlags = flags

        ' if the format was overridded by the user, make no adjustments 
        If Not overrideformat Then
            ' check to see if a style is provided 
            If controlstyle IsNot Nothing Then
                ' Adjust the format based on the control settings, bias towards centered 
                Dim cellalign As DataGridViewContentAlignment = controlstyle.Alignment
                If cellalign.ToString().Contains("Center") Then
                    format.Alignment = StringAlignment.Center
                ElseIf cellalign.ToString().Contains("Right") Then
                    format.Alignment = StringAlignment.Far
                ElseIf cellalign.ToString().Contains("Left") Then
                    format.Alignment = StringAlignment.Near
                End If
            End If
        End If

        Return format
    End Function

    ''' <summary> 
    ''' Scan all the rows and columns to be printed and calculate the 
    ''' overall individual column width (based on largest column value), 
    ''' the header sizes, and determine all the row heights. 
    ''' </summary> 
    ''' <param name="g">The graphics context for all measurements</param> 
    Private Sub MeasurePrintArea(ByVal g As Object) 'ByVal g As Graphics)
        Dim i As Integer, j As Integer

        'added by LCW--columns MUST have names
        For ii As Integer = 0 To dgv.ColumnCount - 1
            With dgv.Columns(ii)
                If .Name = "" Then .Name = "Col" & ii
            End With
        Next

looptop:

        rowHeights = New List(Of Single)(RowsToPrint.Count)
        colWidths = New List(Of Single)(ColsToPrint.Count)
        headerHeight = 0
        footerHeight = 0

        ' temp variables 
        Dim col As DataGridViewColumn
        Dim row As DataGridViewRow

        '----------------------------------------------------------------- 
        ' measure the page headers and footers, including the grid column header cells 
        '----------------------------------------------------------------- 

        ' measure the column headers 
        Dim headerfont As Font = dgv.ColumnHeadersDefaultCellStyle.Font
        If headerfont Is Nothing Then
            headerfont = dgv.DefaultCellStyle.Font
        End If
        headerfont = ScaledFont(headerfont)

        For i = 0 To ColsToPrint.Count - 1

            ' set initial column sizes based on column titles 
            col = DirectCast(ColsToPrint(i), DataGridViewColumn)

            ' measure the title for each column, keep widths and biggest height 

            'lcw: allow for embedded crlf 
            'Dim size As SizeF = MeasureString(g, col.HeaderText, headerfont)
            'colWidths.Add(Size.Width)
            'colheaderheight = (IIf(colheaderheight < Size.Height, Size.Height, colheaderheight))

            Dim maxwidth As Integer = 0
            Dim hdr As String = col.HeaderText.Replace(vbCrLf, vbCr)
            For Each s As String In hdr.Split(vbCr)
                maxwidth = Max(maxwidth, MeasureString(g, s, headerfont).Width)
            Next
            colWidths.Add(maxwidth * 1.05) 'add 5% buffer
            colheaderheight = Max(colheaderheight, MeasureString(g, hdr.Split(vbCr)(0), headerfont).Height * hdr.Split(vbCr).Length)
        Next

        '----------------------------------------------------------------- 
        ' measure the page number 
        '----------------------------------------------------------------- 

        If m_NumberPages Then
            Dim format As Object = ManageStringFormat(m_PageNumberAlignment, m_PageNumberFormatFlags, dgv.DefaultCellStyle, OverrideTitleFormat)
            pageNumberHeight = MeasureString(g, "Page", m_PageNumberFont, format).Height
        End If


        '----------------------------------------------------------------- 
        ' Calc height of header. 
        ' Header height is height of page number, title, subtitle and height of column headers 
        '----------------------------------------------------------------- 

        ' note that we dont count the page number height if it's not on a separate line 
        If m_PageNumberInHeader AndAlso Not m_PageNumberOnSeparateLine Then
            headerHeight += pageNumberHeight
        End If

        If Not [String].IsNullOrEmpty(m_Title) Then
            Dim format As Object = ManageStringFormat(m_TitleAlignment, m_TitleFormatFlags, Nothing, OverrideTitleFormat)
            headerHeight += MeasureString(g, m_Title, m_TitleFont, format).Height
        End If

        If Not [String].IsNullOrEmpty(m_Subtitle) Then
            Dim format As Object = ManageStringFormat(m_SubTitleAlignment, m_SubTitleFormatFlags, Nothing, OverrideTitleFormat)
            headerHeight += MeasureString(g, m_Subtitle, m_SubtitleFont, format).Height
        End If

        If Not [String].IsNullOrEmpty(m_Subtitle2) Then
            Dim format As Object = ManageStringFormat(m_SubTitle2Alignment, m_SubTitle2FormatFlags, Nothing, OverrideTitleFormat)
            headerHeight += MeasureString(g, m_Subtitle2, m_Subtitle2Font, format).Height
        End If

        headerHeight += colheaderheight

        '----------------------------------------------------------------- 
        ' measure the footer, if one is provided. Include the page number if we're printing 
        ' it on the bottom 
        '----------------------------------------------------------------- 

        If Not [String].IsNullOrEmpty(m_Footer) Then
            Dim format As Object = ManageStringFormat(m_FooterAlignment, m_FooterFormatFlags, Nothing, OverrideFooterFormat)
            footerHeight += MeasureString(g, m_Footer, m_FooterFont, format).Height
        End If

        ' note we don't count the page number height if it's not on a separate line 
        If Not m_PageNumberInHeader AndAlso Not m_PageNumberOnSeparateLine Then
            footerHeight += pageNumberHeight
        End If

        footerHeight += m_FooterSpacing

        '----------------------------------------------------------------- 
        ' measure the grid to be printed ... this gets us all the row heights 
        ' and an accurate measure of column widths for the printed area 
        '----------------------------------------------------------------- 

        Dim defaultfont As Font = dgv.DefaultCellStyle.Font
        Dim cellfont As Font
        For i = 0 To RowsToPrint.Count - 1

            row = DirectCast(RowsToPrint(i), DataGridViewRow)
            rowHeights.Add(0)

            ' add row headers if they're visible 
            If dgv.RowHeadersVisible Then
                Dim rhsize As SizeF = MeasureString(g, row.HeaderCell.EditedFormattedValue.ToString(), headerfont)
                RowHeaderWidth = IIf((RowHeaderWidth < rhsize.Width), rhsize.Width, RowHeaderWidth)
            End If
            For j = 0 To ColsToPrint.Count - 1

                ' calculate widths for each column. We're looking for the largest width needed for 
                ' all the rows of data. 
                col = DirectCast(ColsToPrint(j), DataGridViewColumn)

                If row.Cells(col.Name).Style IsNot Nothing AndAlso row.Cells(col.Name).Style.Font IsNot Nothing Then
                    cellfont = row.Cells(col.Name).Style.Font
                Else
                    cellfont = defaultfont
                End If
                cellfont = ScaledFont(cellfont)

                Dim size As SizeF = MeasureString(g, row.Cells(col.Name).EditedFormattedValue.ToString() & "XX", cellfont) 'ass 'XX' so don't get zero height

                If Not IsOverflowCell(col.Index, row.Index) Then
                    colWidths(j) = (IIf(colWidths(j) < size.Width, size.Width, colWidths(j)))
                End If

                'lcw--if embedded crlf, multiply row height
                Dim numlines As Integer = row.Cells(col.Name).EditedFormattedValue.ToString.Split(vbCrLf).Length

                'rowHeights(i) = (IIf(rowHeights(i) < size.Height, size.Height, rowHeights(i)))
                rowHeights(i) = (IIf(rowHeights(i) < size.Height * numlines, size.Height * numlines, rowHeights(i)))
            Next
        Next

        ' total the column widths, including row headers. 
        coltotalwidth = RowHeaderWidth
        For Each w As Single In colWidths
            coltotalwidth += w
        Next

        ' if we're stretching the columns across the page, recalculate each width and 
        ' set the total width to the printable width of the page. 
        If m_ProportionalColumns Then
            Dim ratio As Single = CSng(printWidth) / CSng(coltotalwidth)

            If m_AutoReduceFont And ratio < 1 Then 'try to reduce font size by 10%
                m_FontSizeMult *= 0.95
                GoTo looptop
            End If

            ' adjust row header width 
            RowHeaderWidth = RowHeaderWidth * ratio
            For i = 0 To colWidths.Count - 1
                colWidths(i) = colWidths(i) * ratio
            Next

            ' adjust data cells widths 

            ' and, by definition the total width is the print width 
            coltotalwidth = printWidth

            If m_AllowWordWrap Then
                'adjust row heights to allow for word wrapping
                For i = 0 To RowsToPrint.Count - 1
                    For j = 0 To ColsToPrint.Count - 1
                        row = DirectCast(RowsToPrint(i), DataGridViewRow)
                        If row.Cells(j).HasStyle Then
                            cellfont = row.Cells(j).Style.Font
                        Else
                            cellfont = defaultfont
                        End If
                        rowHeights(i) = Max(rowHeights(i), MeasureStringHeight(g, row.Cells(j).EditedFormattedValue.ToString(), cellfont, colWidths(j)))
                    Next
                Next
            End If
        End If

    End Sub

    Private Function ScaledFont(ByVal OrigFont As Font) As Font
        If m_FontSizeMult = 1 Or OrigFont Is Nothing Then Return OrigFont
        With OrigFont
            Return New Font(.FontFamily, CSng(.Size * m_FontSizeMult), .Style)
        End With
    End Function

    ''' <summary>
    ''' Device independent method to call MeasureString
    ''' </summary>
    Private Function MeasureString(ByVal g As Object, ByVal Text As String, ByVal [Font] As Font, ByVal Format As Object) As SizeF
        If sendToPDF Then
            Return CType(g, XGraphics).MeasureString(Text, GetFont(Font), CType(Format, XStringFormat)).ToSizeF
        Else
            Return CType(g, Graphics).MeasureString(Text, Font, printWidth, CType(Format, StringFormat))
        End If
    End Function

    ''' <summary>
    ''' Device independent method to call MeasureString
    ''' </summary>
    Private Function MeasureString(ByVal g As Object, ByVal Text As String, ByVal [Font] As Font) As SizeF
        If sendToPDF Then
            Return CType(g, XGraphics).MeasureString(Text, GetFont(Font)).ToSizeF
        Else
            Return CType(g, Graphics).MeasureString(Text, Font)
        End If
    End Function

    ''' <summary>
    ''' Device independent method to call MeasureString
    ''' </summary>
    Private Function MeasureStringHeight(ByVal g As Object, ByVal Text As String, ByVal [Font] As Font, ByVal PrintWidth As Integer) As Single
        If sendToPDF Then
            'todo: handle multiline for PDF
            Return CType(g, XGraphics).MeasureString(Text, GetFont(Font)).ToSizeF.Height
        Else
            Return CType(g, Graphics).MeasureString(Text, Font, PrintWidth).Height
        End If
    End Function

    ''' <summary> 
    ''' PrintPage event handler. This routine prints one page. It will 
    ''' skip non-printable pages if the user selected the "some pages" option 
    ''' on the print dialog. 
    ''' </summary> 
    ''' <param name="sender">default object from windows</param> 
    ''' <param name="e">Event info from Windows about the printing</param> 
    Private Sub printDoc_PrintPage(ByVal sender As Object, ByVal e As PrintPageEventArgs)
        PrintPage(e.Graphics, e.HasMorePages)
    End Sub

    Private Sub PrintPage(ByRef graphics As Object, ByRef HasMorePages As Boolean)
        ' flag for handling printing some pages rather than all 
        Dim printthispage As Boolean = False

        ' current printing position 
        Dim printpos As Single = m_PrintMargins.Top

        ' increment page number & check page range 
        CurrentPage += 1
        If (CurrentPage >= fromPage) AndAlso (CurrentPage <= toPage) Then
            printthispage = True
        End If

        ' if this is the first page, then measure the print area 
        If 1 = CurrentPage Then
            m_FontSizeMult = 1.0
            MeasurePrintArea(graphics)
        End If

        '----------------------------------------------------------------- 
        ' scan down heights until we're off this (non-printing) page 
        '----------------------------------------------------------------- 

        While Not printthispage
            ' calculate and increment over the page we're not printing 
            printpos = m_PrintMargins.Top + headerHeight
            While printpos < (pageHeight - footerHeight - m_PrintMargins.Bottom)
                LastRowPrinted += 1
                printpos += rowHeights(LastRowPrinted)
            End While

            ' skip to the next page & see if it's in the print range 
            CurrentPage += 1
            If (CurrentPage >= fromPage) AndAlso (CurrentPage <= toPage) Then
                printthispage = True
            End If

            ' bottom check~ 
            If (LastRowPrinted >= RowsToPrint.Count - 1) OrElse (CurrentPage > toPage) Then
                ' we're done here - nothing more to print 
                HasMorePages = False
                Return
            End If
        End While

        '----------------------------------------------------------------- 
        ' print headers 
        '----------------------------------------------------------------- 

        ' reset printpos as it may have changed during the 'skip pages' routine just above. 
        printpos = m_PrintMargins.Top

        ' print page number if user selected it 
        If m_PageNumberInHeader Then
            ' if we have a page number to print 
            If m_NumberPages Then
                ' ... then print it 
                PrintSection(graphics, printpos, "Page " + CurrentPage.ToString(CultureInfo.CurrentCulture), m_PageNumberFont, m_PageNumberColor, m_PageNumberAlignment, _
                m_PageNumberFormatFlags, OverridePageNumberFormat)

                ' if the page number is not on a separate line, don't "use up" it's vertical space 
                If Not m_PageNumberOnSeparateLine Then
                    printpos -= pageNumberHeight
                End If
            End If
        End If

        ' print title if provided 
        If Not [String].IsNullOrEmpty(m_Title) And (CurrentPage = 1 Or m_RepeatTitles) Then
            PrintSection(graphics, printpos, m_Title, m_TitleFont, m_TitleColor, m_TitleAlignment, m_TitleFormatFlags, OverrideTitleFormat)
        End If

        ' print subtitle if provided 
        If Not [String].IsNullOrEmpty(m_Subtitle) And (CurrentPage = 1 Or m_RepeatTitles) Then
            PrintSection(graphics, printpos, m_Subtitle, m_SubtitleFont, m_SubtitleColor, m_SubTitleAlignment, m_SubTitleFormatFlags, OverrideSubtitleFormat)
        End If

        ' print subtitle2 if provided 
        If Not [String].IsNullOrEmpty(m_Subtitle2) And (CurrentPage = 1 Or m_RepeatTitles) Then
            PrintSection(graphics, printpos, m_Subtitle2, m_Subtitle2Font, m_Subtitle2Color, m_SubTitle2Alignment, m_SubTitle2FormatFlags, OverrideSubtitleFormat)
        End If

        ' print column headers 
        PrintColumnHeaders(graphics, printpos)

        '----------------------------------------------------------------- 
        ' print rows until the page is complete 
        '----------------------------------------------------------------- 

        If rowHeights.Count = 0 Then Exit Sub

        While printpos + rowHeights(LastRowPrinted + 1) < (pageHeight - footerHeight - m_PrintMargins.Bottom)
            LastRowPrinted += 1
            PrintRow(graphics, printpos, DirectCast((RowsToPrint(LastRowPrinted)), DataGridViewRow))

            ' bottom check, we're really really done when there's no more data 
            If LastRowPrinted >= RowsToPrint.Count - 1 Then
                ' set no more pages flag 
                HasMorePages = False

                'todo: go to next page if endnote is too large
                'print endnote if provided 
                If Not [String].IsNullOrEmpty(m_EndNote) Then
                    PrintSection(graphics, printpos, m_EndNote, m_EndNoteFont, m_EndNoteColor, m_EndNoteAlignment, m_EndNoteFormatFlags, OverrideSubtitleFormat)
                End If

                ' print a footer for this page 
                PrintFooter(graphics, printpos)

                ' reset counters since we'll go through this twice if we print from preview 
                LastRowPrinted = -1
                CurrentPage = 0

                ' return 
                Return
            End If
        End While

        '----------------------------------------------------------------- 
        ' print footer 
        '----------------------------------------------------------------- 
        PrintFooter(graphics, printpos)

        '----------------------------------------------------------------- 
        ' bottom check, see if this is the last page to print 
        '----------------------------------------------------------------- 

        If CurrentPage >= toPage Then
            ' tell the caller we're complete 
            HasMorePages = False

            ' reset counters since we'll go through this twice if we print from preview 
            LastRowPrinted = -1
            CurrentPage = 0
        Else
            ' we're not done yet 
            HasMorePages = True
        End If

        Return
    End Sub

    ''' <summary> 
    ''' Print a header or footer section. Used for page numbers and titles 
    ''' </summary> 
    ''' <param name="g">Graphic context to print in</param> 
    ''' <param name="pos">Track vertical space used; 'y' location</param> 
    ''' <param name="text">String to print</param> 
    ''' <param name="font">Font to use for printing</param> 
    ''' <param name="color">Color to print in</param> 
    ''' <param name="alignment">Alignment - print to left, center or right</param> 
    ''' <param name="flags">String format flags</param> 
    ''' <param name="useroverride">True if the user overrode the alignment or flags</param> 
    Private Sub PrintSection(ByVal g As Object, ByRef pos As Single, ByVal text As String, ByVal [font] As Font, ByVal color As Color, ByVal alignment As StringAlignment, ByVal flags As StringFormatFlags, ByVal useroverride As Boolean)
        '' string formatting setup 
        Dim printformat As Object = ManageStringFormat(alignment, flags, Nothing, useroverride)

        ' measure string 
        Dim printsize As SizeF = MeasureString(g, text, font, printformat)

        ' build area to print within (handle case of single tab at beginning of string
        Dim printarea As New RectangleF(CSng(m_PrintMargins.Left), pos, CSng(printWidth), printsize.Height)

        ' do the actual print 
        'note: because PDFSharp cannot handle string with embedded crlf, do this here
        Dim ht As Double = 0
        If text IsNot Nothing Then
            For Each s As String In text.Replace(vbCrLf, vbCr).Split(vbCr)
                g.DrawString(s, GetFont(font), New SolidBrush(color), printarea, printformat)
                printarea.Offset(0, MeasureString(g, s & "XXX", font, printformat).Height)
                ht += printarea.Height
            Next
        End If

        ' track "used" vertical space 
        pos += ht

        'LCW: add a 50% gap before the next section
        'pos += 0.5 * MeasureString(g, "XXX", font, printformat).Height
    End Sub

    ''' <summary>
    ''' Sharp PDF requires that fonts use the World units; this facilitates DrawString
    ''' </summary>
    Function GetFont(ByVal [Font] As Font) As Object
        If sendToPDF Then
            Return New XFont(Font.Name, Font.Size, Font.Style)
        Else
            Return Font
        End If
    End Function

    ''' <summary> 
    ''' Print the footer. This handles the footer spacing, and printing the page number 
    ''' at the bottom of the page (if the page number is not in the header). 
    ''' </summary> 
    ''' <param name="g">Graphic context to print in</param> 
    ''' <param name="pos">Track vertical space used; 'y' location</param> 
    Private Sub PrintFooter(ByVal g As Object, ByRef pos As Single)
        ' print last footer. Note: need to force printpos to the bottom of the page 
        ' as we may have run out of data anywhere on the page 
        pos = pageHeight - footerHeight - m_PrintMargins.Bottom

        ' add spacing 
        pos += m_FooterSpacing

        ' print the footer 
        PrintSection(g, pos, m_Footer, m_FooterFont, m_FooterColor, m_FooterAlignment, m_FooterFormatFlags, OverrideFooterFormat)

        ' print the page number if it's on the bottom. 
        If Not m_PageNumberInHeader Then
            If m_NumberPages Then
                ' if the pageno is not on a separate line, push the print location up by its height. 
                If Not m_PageNumberOnSeparateLine Then pos = pos - pageNumberHeight
                ' print the page number 
                PrintSection(g, pos, "Page " + CurrentPage.ToString(CultureInfo.CurrentCulture), m_PageNumberFont, m_PageNumberColor, m_PageNumberAlignment, m_PageNumberFormatFlags, OverridePageNumberFormat)
            End If
        End If
    End Sub

    ''' <summary> 
    ''' Print the column headers. Most printing format info is retrieved from the 
    ''' source DataGridView. 
    ''' </summary> 
    ''' <param name="g">Graphics Context to print within</param> 
    ''' <param name="pos">Track vertical space used; 'y' location</param> 
    Private Sub PrintColumnHeaders(ByVal g As Object, ByRef pos As Single)
        '----------------------------------------------------------------- 
        ' setup printing 
        '----------------------------------------------------------------- 

        ' track printing location across the page. start position is hard left, 
        ' adjusted for the row headers. Note rowheaderwidth is 0 if row headers are not printed 
        Dim xcoord As Single = m_PrintMargins.Left + RowHeaderWidth

        ' set up colors, fonts for header printing 
        Dim foreground As Color = IIf(Not (dgv.ColumnHeadersDefaultCellStyle.ForeColor.IsEmpty), dgv.ColumnHeadersDefaultCellStyle.ForeColor, dgv.DefaultCellStyle.ForeColor)

        Dim background As Color = IIf(Not (dgv.ColumnHeadersDefaultCellStyle.BackColor.IsEmpty), dgv.ColumnHeadersDefaultCellStyle.BackColor, dgv.DefaultCellStyle.BackColor)

        Dim font As Font = IIf((dgv.ColumnHeadersDefaultCellStyle.Font IsNot Nothing), dgv.ColumnHeadersDefaultCellStyle.Font, dgv.DefaultCellStyle.Font)
        font = ScaledFont(font)

        ' define print rectangle 
        Dim printarea As New RectangleF(xcoord, pos, coltotalwidth - RowHeaderWidth, colheaderheight)

        ' set brushes for printing 
        Dim forebrush As New SolidBrush(foreground)
        Dim backbrush As New SolidBrush(background)

        ' set the pen for drawing the grid lines 
        Dim lines As New Pen(dgv.GridColor, 1)

        ' set the printing format for individual header cells 
        Dim format As Object = ManageStringFormat(m_HeaderCellAlignment, m_HeaderCellFormatFlags, dgv.ColumnHeadersDefaultCellStyle, OverrideCellHeaderFormat)

        '----------------------------------------------------------------- 
        ' Now that we're all setup, start printing the column headers 
        '----------------------------------------------------------------- 

        ' fill in the background 
        If sendToPDF Then
            With CType(g, XGraphics)
                .DrawRectangle(backbrush, printarea)
                .DrawRectangle(lines, printarea)
            End With
        Else
            With CType(g, Graphics)
                .FillRectangle(backbrush, printarea)
            End With
        End If

        ' write and draw each cell 
        Dim col As DataGridViewColumn
        For i As Integer = 0 To ColsToPrint.Count - 1
            col = DirectCast(ColsToPrint(i), DataGridViewColumn)

            ' set print area for this individual cell 
            Dim cellprintarea As New RectangleF(xcoord, pos, colWidths(i), colheaderheight)

            ' draw the text 
            'g.DrawString(col.HeaderText, GetFont(font), forebrush, cellprintarea, format)

            'lcw--do following to allow multi-row cells if have embedded vbcr or vbcrlf
            Dim text As String = col.HeaderText
            For Each s As String In text.Replace(vbCrLf, vbCr).Split(vbCr)
                g.DrawString(s, GetFont(font), forebrush, cellprintarea, format)
                cellprintarea.Offset(0, MeasureString(g, s, font, format).Height)
            Next

            ' draw the borders - default to the dgv's border setting 
            If dgv.ColumnHeadersBorderStyle <> DataGridViewHeaderBorderStyle.None Then
                If sendToPDF Then
                    CType(g, XGraphics).DrawRectangle(lines, xcoord, pos, colWidths(i), colheaderheight)
                Else
                    CType(g, Graphics).DrawRectangle(lines, xcoord, pos, colWidths(i), colheaderheight)
                End If
            End If

            xcoord += colWidths(i)
        Next

        ' all done, consume "used" vertical space 
        pos += colheaderheight
    End Sub

    ''' <summary> 
    ''' Print one row of the DataGridView. Most printing format info is retrieved 
    ''' from the DataGridView. 
    ''' </summary> 
    ''' <param name="g">Graphics Context to print within</param> 
    ''' <param name="pos">Track vertical space used; 'y' location</param> 
    ''' <param name="row">The row that will be printed</param> 
    Private Sub PrintRow(ByVal g As Object, ByRef pos As Single, ByVal row As DataGridViewRow)
        '----------------------------------------------------------------- 
        ' setup printing 
        '----------------------------------------------------------------- 

        ' track printing location across the page 
        Dim xcoord As Single = m_PrintMargins.Left

        ' set up colors, fonts for header printing 
        Dim foreground As Color = IIf(Not (row.DefaultCellStyle.ForeColor.IsEmpty), row.DefaultCellStyle.ForeColor, dgv.DefaultCellStyle.ForeColor)

        Dim background As Color = IIf(Not (row.DefaultCellStyle.BackColor.IsEmpty), row.DefaultCellStyle.BackColor, dgv.DefaultCellStyle.BackColor)

        Dim font As Font = IIf((row.DefaultCellStyle.Font IsNot Nothing), row.DefaultCellStyle.Font, dgv.DefaultCellStyle.Font)
        font = ScaledFont(font)

        ' define print rectangle 
        Dim printarea As New RectangleF(xcoord, pos, coltotalwidth, rowHeights(LastRowPrinted))

        ' set brushes for printing 
        Dim forebrush, backbrush As Object
        If sendToPDF Then
            forebrush = New XSolidBrush(foreground)
            backbrush = New XSolidBrush(background)
        Else
            forebrush = New SolidBrush(foreground)
            backbrush = New SolidBrush(background)
        End If

        ' set the pen for drawing the grid lines 
        Dim lines As New Pen(dgv.GridColor, 1)

        ' set the printing format for individual cells 
        Dim format As Object = ManageStringFormat(m_CellAlignment, m_CellFormatFlags, row.DefaultCellStyle, OverrideCellFormat)

        '----------------------------------------------------------------- 
        ' Now that we're all setup, start printing the row as individual cells 
        '----------------------------------------------------------------- 

        ' fill in the row background 
        If sendToPDF Then
            With CType(g, XGraphics)
                .DrawRectangle(backbrush, printarea)
                .DrawRectangle(lines, printarea)
            End With
        Else
            With CType(g, Graphics)
                .FillRectangle(backbrush, printarea)
            End With
        End If

        ' print row headers 
        If dgv.RowHeadersVisible Then
            ' set printing format for the row headers 
            Dim RowHeaderFormat As Object = ManageStringFormat(m_HeaderCellAlignment, m_HeaderCellFormatFlags, row.DefaultCellStyle, OverrideCellHeaderFormat)

            ' set print area for this individual cell 
            Dim HeaderCellPrintArea As New RectangleF(xcoord, pos, RowHeaderWidth, rowHeights(LastRowPrinted))

            ' draw the text for the row header cell 
            g.DrawString(row.HeaderCell.EditedFormattedValue.ToString(), GetFont(font), forebrush, HeaderCellPrintArea, RowHeaderFormat)

            ' draw the borders - default to the dgv's border setting 
            If dgv.RowHeadersBorderStyle <> DataGridViewHeaderBorderStyle.None Then
                If sendToPDF Then
                    CType(g, XGraphics).DrawRectangle(lines, xcoord, pos, RowHeaderWidth, rowHeights(LastRowPrinted))
                Else
                    CType(g, Graphics).DrawRectangle(lines, xcoord, pos, RowHeaderWidth, rowHeights(LastRowPrinted))
                End If
            End If

            ' track horizontal space used 
            xcoord += RowHeaderWidth
        End If

        Dim xxcoord As Single = xcoord
        For i As Integer = 0 To ColsToPrint.Count - 1
            ' draw the borders - default to the dgv's cell border setting (unless first column allows overflow
            If dgv.CellBorderStyle <> BorderStyle.None And Not IsOverflowCell(0, row.Index) Then
                If sendToPDF Then
                    CType(g, XGraphics).DrawRectangle(lines, xxcoord, pos, colWidths(i), rowHeights(LastRowPrinted))
                Else
                    CType(g, Graphics).DrawRectangle(lines, xxcoord, pos, colWidths(i), rowHeights(LastRowPrinted))
                End If
            End If
            xxcoord += colWidths(i)
        Next

        ' write and draw each cell 
        Dim col As DataGridViewColumn
        For i As Integer = 0 To ColsToPrint.Count - 1

            ' access the column being printed 
            col = DirectCast(ColsToPrint(i), DataGridViewColumn)

            ' set print area for this individual cell 
            Dim cellprintarea As New RectangleF(xcoord, pos, colWidths(i), rowHeights(LastRowPrinted))

            'LCW: if didn't override cell format, use column format
            'format = ManageStringFormat(m_CellAlignment, m_CellFormatFlags, dgv.Columns(i).DefaultCellStyle, OverrideCellFormat)
            Dim cell As DataGridViewCell = row.Cells(col.Index)
            'format = ManageStringFormat(m_CellAlignment, m_CellFormatFlags, IIf(cell.HasStyle AndAlso cell.Style.Alignment <> DataGridViewContentAlignment.NotSet, cell.Style, dgv.Columns(i).DefaultCellStyle), OverrideCellFormat)
            format = ManageStringFormat(m_CellAlignment, m_CellFormatFlags, IIf(cell.HasStyle AndAlso cell.Style.Alignment <> DataGridViewContentAlignment.NotSet, cell.Style, col.DefaultCellStyle), OverrideCellFormat)


            ' draw the borders - default to the dgv's border setting 
            Dim cellrect As New RectangleF(xcoord, pos, colWidths(i), rowHeights(LastRowPrinted))

            'If dgv.BorderStyle <> BorderStyle.None Then
            '    If sendToPDF Then
            '        CType(g, XGraphics).DrawRectangle(lines, cellrect)
            '    Else
            '        CType(g, Graphics).DrawRectangle(lines, xcoord, pos, colWidths(i), rowHeights(LastRowPrinted))
            '    End If
            'End If

            Dim text As String = cell.EditedFormattedValue.ToString()

            ' if allow cell overlap.....
            Dim IsOverflow As Boolean = IsOverflowCell(col.Index, row.Index)
            If IsOverflow Then
                cellprintarea.Width = 0 'so doesn't clip

                'must fill background of text so can't see vertical cell lines
                'cellrect.Height -= lines.Width * 2
                cellrect.Width = MeasureString(g, text, font, format).Width
                cellrect.Offset(lines.Width, lines.Width)
                If sendToPDF Then
                    CType(g, XGraphics).DrawRectangle(backbrush, cellrect)
                Else
                    CType(g, Graphics).FillRectangle(backbrush, cellrect)
                End If
            End If

            ' draw the text for the cell at the row / col intersection 
            'g.DrawString(row.Cells(col.Index).EditedFormattedValue.ToString(), GetFont(font), forebrush, cellprintarea, format)

            'lcw--do following to allow multi-row cells if have embedded vbcr or vbcrlf
            If row.Cells(col.Index).EditedFormattedValue IsNot Nothing Then
                For Each s As String In text.Replace(vbCrLf, vbCr).Split(vbCr)
                    g.DrawString(s, GetFont(font), forebrush, cellprintarea, format)
                    cellprintarea.Offset(0, MeasureString(g, s, font, format).Height)
                Next
            End If

            '' draw the borders - default to the dgv's border setting 
            'If dgv.BorderStyle <> BorderStyle.None Then
            '    If sendToPDF Then
            '        CType(g, XGraphics).DrawRectangle(lines, xcoord, pos, colWidths(i), rowHeights(LastRowPrinted))
            '    Else
            '        CType(g, Graphics).DrawRectangle(lines, xcoord, pos, colWidths(i), rowHeights(LastRowPrinted))
            '    End If
            'End If

            ' track horizontal space used 
            xcoord += colWidths(i)
        Next

        ' all done, consume "used" vertical space 
        pos += rowHeights(LastRowPrinted)
    End Sub

End Class
